package com.onlinepay.util;

import com.alibaba.fastjson.JSONObject;
import com.onlinepay.exception.EncryptException;
import org.json.JSONArray;
import sun.misc.BASE64Encoder;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.io.*;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;


public class RSAUtil {

	/** Cipher 运算法则公式 = 算法/模式/填充 */
	public static final String RSA_ECB_PKCS1Padding = "RSA/ECB/PKCS1Padding";

	public static final String SIGN_SHA1_WITH_RSA = "SHA1WithRSA";
	public static final String SIGN_SHA256_WITH_RSA = "SHA256WithRSA";
	public static final String SIGN_MD5_WITH_RSA = "MD5withRSA";
	private static String algorithm = "RSA";

	// --------------------------------- SIGN -----------------------------------//

	public static String sign(String content, String privateKey, String input_charset) throws Exception {
		if (privateKey == null) {
			throw new Exception("加密私钥为空, 请设置");
		}
		PrivateKey privateKeyInfo = getPrivateKey(privateKey);
		return sign(content, privateKeyInfo, input_charset);
	}


	public static String sign(String content, PrivateKey privateKey, String input_charset) throws Exception {
		if (privateKey == null) {
			throw new Exception("加密私钥为空, 请设置");
		}
		Signature signature = Signature.getInstance(SIGN_SHA1_WITH_RSA);
		signature.initSign(privateKey);
		signature.update(content.getBytes(input_charset));
		return Base64.encode(signature.sign());
	}


	public static String signSHA256RSA(String content, String privateKey, String input_charset)
			throws Exception {
		if (privateKey == null) {
			throw new Exception("加密私钥为空, 请设置");
		}
		PrivateKey privateKeyInfo = getPrivateKey(privateKey);
		return signSHA256RSA(content, privateKeyInfo, input_charset);
	}


	public static String signSHA256RSA(String content, PrivateKey privateKey, String input_charset)
			throws Exception {
		if (privateKey == null) {
			throw new Exception("加密私钥为空, 请设置");
		}
		Signature signature = Signature.getInstance(SIGN_SHA256_WITH_RSA);
		signature.initSign(privateKey);
		signature.update(content.getBytes(input_charset));
		return Base64.encode(signature.sign());
	}

	public static String signMD5RSA(String content, PrivateKey privateKey, String input_charset)
			throws Exception {
		if (privateKey == null) {
			throw new Exception("加密私钥为空, 请设置");
		}
		Signature signature = Signature.getInstance(SIGN_MD5_WITH_RSA);
		signature.initSign(privateKey);
		signature.update(content.getBytes(input_charset));
		return Base64.encode(signature.sign());
	}

	// --------------------------------- VERIFY -----------------------------------//
	public static boolean verify(String content, String sign, String publicKey, String input_charset) {
		try {
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			byte[] encodedKey = Base64.decode(publicKey);
			PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
			return verify(content, sign, pubKey, input_charset);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;

	}


	public static boolean verify(String content, String sign, PublicKey publicKey, String inputCharset) {
		try {
			Signature signature = Signature.getInstance(SIGN_SHA1_WITH_RSA);
			signature.initVerify(publicKey);
			signature.update(content.getBytes(inputCharset));
			boolean bverify = signature.verify(Base64.decode(sign));
			return bverify;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}

	public static boolean verifyMD5RSA(String content, String sign, PublicKey publicKey, String inputCharset) {
		try {
			Signature signature = Signature.getInstance(SIGN_MD5_WITH_RSA);
			signature.initVerify(publicKey);
			signature.update(content.getBytes(inputCharset));
			boolean bverify = signature.verify(Base64.decode(sign));
			return bverify;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}


	public static boolean verifySHA256(String content, String sign, String publicKey, String inputCharset) {
		try {
			return verifySHA256(content, sign, getPublicKey(publicKey), inputCharset);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}

	public static boolean verifySHA256(String content, String sign, PublicKey publicKey, String inputCharset) {
		try {
			Signature signature = Signature.getInstance(SIGN_SHA256_WITH_RSA);
			signature.initVerify(publicKey);
			signature.update(content.getBytes(inputCharset));
			boolean bverify = signature.verify(Base64.decode(sign));
			return bverify;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return false;
	}


	public static String encrypt(Key key, String src) throws EncryptException {
		Cipher cipher = null;
		Provider provider = null;
		try {
			//byte[] data = src.getBytes(ENCODING);
			//            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
			provider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
			cipher =  Cipher.getInstance(algorithm,provider);


			//还有部分没有释放
			cipher.init(Cipher.ENCRYPT_MODE, key);
			byte[] b = src.getBytes();
			byte[] b1 = cipher.doFinal(b);
			BASE64Encoder encoder = new BASE64Encoder();
			return encoder.encode(b1).replaceAll("\\s","");


            /*cipher.init(Cipher.ENCRYPT_MODE, key);
            int blockSize = cipher.getBlockSize();//获得加密块大小，如：加密前数据为128个byte，而key_size=1024 加密块大小为127 byte,加密后为128个byte;因此共有2个加密块，第一个127 byte第二个为1个byte
            int outputSize = cipher.getOutputSize(data.length);//获得加密块加密后块大小
            int leavedSize = data.length % blockSize;
            int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize;
            byte[] raw = new byte[outputSize * blocksSize];
            int i = 0;
            while (data.length - i * blockSize > 0) {
                if (data.length - i * blockSize > blockSize)
                    cipher.doFinal(data, i * blockSize, blockSize, raw, i * outputSize);
                else
                    cipher.doFinal(data, i * blockSize, data.length - i * blockSize, raw, i * outputSize);
//这里面doUpdate方法不可用，查看源代码后发现每次doUpdate后并没有什么实际动作除了把byte[]放到ByteArrayOutputStream中，而最后doFinal的时候才将所有的byte[]进行加密，可是到了此时加密块大小很可能已经超出了OutputSize所以只好用dofinal方法。
                i++;
            }
            return bytesToString(raw);*/
		} catch (Exception e) {
			e.printStackTrace();
			throw new EncryptException(e.getMessage());
		} finally {
			provider.clear();
			provider = null;
			cipher = null;
		}
	}




	// --------------------------------- DECRYPT -------------------------------//

	public static String decryptByPublic(String content, String publicKey, String input_charset) throws Exception {
		PublicKey pubKey = getPublicKey(publicKey);
		return decrypt(content, input_charset, pubKey);
	}

	public static String decrypt(String content, String private_key, String input_charset) throws Exception {
		PrivateKey prikey = getPrivateKey(private_key);
		return decrypt(content, input_charset, prikey);
	}

	public static String decrypt(String content, PrivateKey privateKey, String input_charset,
                                 String transformation) throws Exception {
		return decrypt(content, input_charset, privateKey, 128);
	}

	private static String decrypt(String content, String input_charset, Key key)
			throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException,
            IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, NoSuchProviderException {
		return decrypt(content, input_charset, key, 128);
	}

	public static String decrypt(String content, String input_charset, Key key, int contentSize)
			throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException,
            IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, NoSuchProviderException {
		Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1Padding, new org.bouncycastle.jce.provider.BouncyCastleProvider());
		cipher.init(Cipher.DECRYPT_MODE, key);
		InputStream ins = new ByteArrayInputStream(Base64.decode(content));

		ByteArrayOutputStream writer = new ByteArrayOutputStream();
		// rsa解密的字节大小最多是128，将需要解密的内容，按128位拆开解密
		byte[] buf = new byte[contentSize];
		int bufl;
		while ((bufl = ins.read(buf)) != -1) {
			byte[] block = null;

			if (buf.length == bufl) {
				block = buf;
			} else {
				block = new byte[bufl];
				for (int i = 0; i < bufl; i++) {
					block[i] = buf[i];
				}
			}
			writer.write(cipher.doFinal(block));
		}
		return new String(writer.toByteArray(), input_charset);
	}

	public static String decryptNoProvider(String content, String input_charset, Key key,
                                           int contentSize) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IOException,
            IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException, NoSuchProviderException {
		Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1Padding);
		cipher.init(Cipher.DECRYPT_MODE, key);
		InputStream ins = new ByteArrayInputStream(Base64.decode(content));

		ByteArrayOutputStream writer = new ByteArrayOutputStream();
		// rsa解密的字节大小最多是128，将需要解密的内容，按128位拆开解密
		byte[] buf = new byte[contentSize];
		int bufl;
		while ((bufl = ins.read(buf)) != -1) {
			byte[] block = null;

			if (buf.length == bufl) {
				block = buf;
			} else {
				block = new byte[bufl];
				for (int i = 0; i < bufl; i++) {
					block[i] = buf[i];
				}
			}
			writer.write(cipher.doFinal(block));
		}
		return new String(writer.toByteArray(), input_charset);
	}


	/**
	 * 得到私钥
	 *
	 * @param key
	 *            密钥字符串（经过base64编码）
	 * @throws Exception
	 */
	public static PrivateKey getPrivateKey(String key) throws Exception {
		byte[] keyBytes = buildPKCS8Key(key);
		PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
		return privateKey;
	}

	private static byte[] buildPKCS8Key(String privateKey) throws IOException {
		if (privateKey.contains("-----BEGIN PRIVATE KEY-----")) {
			return Base64.decode(privateKey.replaceAll("-----\\w+ PRIVATE KEY-----", ""));
		} else if (privateKey.contains("-----BEGIN RSA PRIVATE KEY-----")) {
			final byte[] innerKey = Base64.decode(privateKey.replaceAll("-----\\w+ RSA PRIVATE KEY-----", ""));
			final byte[] result = new byte[innerKey.length + 26];
			System.arraycopy(Base64.decode("MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKY="), 0, result, 0, 26);
			System.arraycopy(BigInteger.valueOf(result.length - 4).toByteArray(), 0, result, 2, 2);
			System.arraycopy(BigInteger.valueOf(innerKey.length).toByteArray(), 0, result, 24, 2);
			System.arraycopy(innerKey, 0, result, 26, innerKey.length);
			return result;
		} else {
			return Base64.decode(privateKey);
		}
	}

	public static KeyInfo getKeyInfoByPFXStr(String pfxStr, String password) throws KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
		// FileInputStream fis = new FileInputStream(pfxPath);
		InputStream fis = new ByteArrayInputStream(Base64.decode(pfxStr));
		KeyStore ks = KeyStore.getInstance("PKCS12");
		ks.load(fis, password.toCharArray());
		fis.close();
		Enumeration<String> enumas = ks.aliases();
		String keyAlias = null;
		if (enumas.hasMoreElements())// we are readin just one certificate.
		{
			keyAlias = enumas.nextElement();
		}

		KeyInfo keyInfo = new KeyInfo();

		PrivateKey prikey = (PrivateKey) ks.getKey(keyAlias, password.toCharArray());
		Certificate cert = ks.getCertificate(keyAlias);
		PublicKey pubkey = cert.getPublicKey();

		keyInfo.privateKey = prikey;
		keyInfo.publicKey = pubkey;
		return keyInfo;
	}

	public static class KeyInfo {

		PublicKey publicKey;
		PrivateKey privateKey;

		public PublicKey getPublicKey() {
			return publicKey;
		}

		public PrivateKey getPrivateKey() {
			return privateKey;
		}
	}

	public static PublicKey getPublicKey(String key) throws Exception {
		if (key == null) {
			throw new Exception("加密公钥为空, 请设置");
		}
		key = key.replaceAll("\\-{5}[\\w\\s]+\\-{5}[\\r\\n|\\n]", "");
		byte[] buffer = Base64.decode(key);
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
		return keyFactory.generatePublic(keySpec);
	}

	public static byte[] encryptByPrivateKey(byte[] data, String key) throws Exception {
		byte[] keyBytes = Base64.decode(key);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
		Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1Padding); //"RSA/ECB/PKCS1Padding"
		cipher.init(Cipher.ENCRYPT_MODE, privateKey);
		return cipher.doFinal(data);
	}

	public static byte[] decryptByPublicKey(byte[] data, String key) throws Exception {
		byte[] keyBytes = Base64.decode(key);
		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance("RSA");
		Key publicKey = keyFactory.generatePublic(x509KeySpec);
		Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1Padding); //"RSA/ECB/PKCS1Padding"
		cipher.init(2, publicKey);
		return cipher.doFinal(data);
	}

    public static String readFile(String filePath, String charSet) throws Exception {
        FileInputStream fileInputStream = new FileInputStream(filePath);
        try {
            FileChannel fileChannel = fileInputStream.getChannel();
            ByteBuffer byteBuffer = ByteBuffer.allocate((int) fileChannel.size());
            fileChannel.read(byteBuffer);
            byteBuffer.flip();
            return new String(byteBuffer.array(), charSet);
        } finally {
            fileInputStream.close();
        }
    }

	public static Object ReadObject(String fineName) throws IOException, ClassNotFoundException {
		FileInputStream fis = new FileInputStream(new File(fineName));
		ObjectInputStream ois = new ObjectInputStream(fis);
		Object o = ois.readObject();
		ois.close();
		fis.close();
		return o;
	}
	public static String getTreeValue(Map<String, Object> toSortData, boolean emptyParamIsSign) {
		TreeMap<String, Object> params = new TreeMap<String, Object>();
		params.putAll(toSortData);
		StringBuffer orgin = new StringBuffer();
		Iterator<String> iter = params.keySet().iterator();
		while (iter.hasNext()) {
			String key = (String) iter.next();
			String value = String.valueOf(params.get(key));

			//将treeMap中传递的value为ArrayList类型的转换为JSONArray类型
			Object valueObj = params.get(key);
			if (Objects.nonNull(valueObj) && valueObj instanceof ArrayList) {
				JSONArray jsonArray = new JSONArray((ArrayList<?>) valueObj);
				value = jsonArray.toString();
			}

			if (emptyParamIsSign) {//空值参数参与签名
				orgin.append("&").append(key).append("=").append(value);
			} else {//空值参数不参与签名
				if (value != null && value != "") {
					orgin.append("&").append(key).append("=").append(value);
				}
			}
		}
		return orgin.toString().substring(1);
	}

	public static Map<String, Object> objectToMap(Object obj) throws IllegalAccessException {
		Map<String, Object> map = new HashMap<>();
		Class<?> clazz = obj.getClass();
		for (Field field : clazz.getDeclaredFields()) {
			field.setAccessible(true);
			if (field.get(obj) == null) {
				continue;
			}
			map.put(field.getName(), field.get(obj));
		}
		return map;
	}

	public static String getStringTreeValue(Map<String, String> toSortData, boolean emptyParamIsSign) {
		TreeMap<String, Object> params = new TreeMap<String, Object>();
		params.putAll(toSortData);
		StringBuffer orgin = new StringBuffer();
		Iterator<String> iter = params.keySet().iterator();
		while (iter.hasNext()) {
			String key = (String) iter.next();
			String value = String.valueOf(params.get(key));
			if (emptyParamIsSign) {//空值参数参与签名
				orgin.append("&").append(key).append("=").append(value);
			} else {//空值参数不参与签名
				if (value != null && value != "") {
					orgin.append("&").append(key).append("=").append(value);
				}
			}
		}
		return orgin.toString().substring(1);
	}
	public static Map<String, Object> convertToMap(Object obj) throws IllegalAccessException {
		Map<String, Object> resultMap = new HashMap<>();
		Class<?> objClass = obj.getClass();

		// 获取所有字段（包括私有字段）
		Field[] fields = objClass.getDeclaredFields();
		for (Field field : fields) {
			field.setAccessible(true); // 设置字段可访问
			Object value = String.valueOf(field.get(obj));
			if ("null".equals(value)) {
				continue;
			}
			if (isPrimitiveOrWrapper(field.getType()) || field.getType() == String.class) {
				resultMap.put(field.getName(), value);
			} else {
				resultMap.put(field.getName(), value);
			}
		}

		return resultMap;
	}
	public static String getNewTree(String jsonString, boolean emptyParamIsSign) {
		HashMap hashMap = JSONObject.parseObject(jsonString, HashMap.class);
		TreeMap<String, Object> params = new TreeMap<String, Object>();
		params.putAll(hashMap);
		StringBuffer orgin = new StringBuffer();
		Iterator<String> iter = params.keySet().iterator();
		while (iter.hasNext()) {
			String key = (String) iter.next();
			if (key.equals("sign")) {
				continue;
			}
			String value = String.valueOf(params.get(key));
			if (emptyParamIsSign) {//空值参数参与签名
				orgin.append("&").append(key).append("=").append(value);
			} else {//空值参数不参与签名
				if (value != null && value != "") {
					orgin.append("&").append(key).append("=").append(value);
				}
			}
		}
		return orgin.toString().substring(1);
	}

	private static boolean isPrimitiveOrWrapper(Class<?> type) {
		return type.isPrimitive() ||
				type == Boolean.class ||
				type == Character.class ||
				type == Byte.class ||
				type == Short.class ||
				type == Integer.class ||
				type == Long.class ||
				type == Float.class ||
				type == Double.class;
	}

}
